/* Copyright (c) 2008 Google Inc.
*  Copyright (c) 2009 Pierre Henri Kuate.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

namespace org.opensocial.client
{
    /// <summary> An object representing a collection of OpenSocial requests. Instances may be
    /// instantiated at the top level; individual requests are created via static
    /// methods of OpenSocialClient and added to the batch request with the add
    /// method. The requests are then submitted to the container with the send method.
    /// </summary>
    /// <author>  Jason Cooper
    /// </author>
    public class OpenSocialBatch
    {
        private readonly System.Collections.Generic.List<IOpenSocialRequest> requests;

        public OpenSocialBatch()
        {
            requests = new System.Collections.Generic.List<IOpenSocialRequest>();
        }

        /// <summary> Sets the id property of the passed request and adds it to the internal
        /// collection.
        /// </summary>
        /// <param name="request">OpenSocialRequest object to add to internal collection
        /// </param>
        /// <param name="id">Request label, used to extract data from OpenSocialResponse
        /// object returned
        /// </param>
        public OpenSocialBatch addRequest(IOpenSocialRequest request, string id)
        {
            request.Id = id;
            requests.Add(request);
            return this;
        }

        /// <summary> Calls one of two private methods which submit the queued requests to the
        /// container given the properties of the OpenSocialClient object passed in.
        /// </summary>
        /// <param name="client">OpenSocialClient object with REST_BASE_URI or RPC_ENDPOINT
        /// properties set
        /// </param>
        /// <returns> Object encapsulating the data requested from the container
        /// </returns>
        /// <throws>  OpenSocialRequestException </throws>
        /// <throws>  JSONException </throws>
        /// <throws>  OAuthException </throws>
        /// <throws>  IOException </throws>
        /// <throws>  URISyntaxException </throws>
        public OpenSocialResponse send(OpenSocialClient client)
        {
            if (requests.Count == 0)
                throw new OpenSocialRequestException("One or more requests must be added before sending batch");

            string rpcEndpoint = client.getProperty(OpenSocialClient.Properties.RPC_ENDPOINT);
            string restBaseUri = client.getProperty(OpenSocialClient.Properties.REST_BASE_URI);

            if (rpcEndpoint == null && restBaseUri == null)
                throw new OpenSocialRequestException("REST base URI or RPC endpoint required");

            if (client.UseRpc)
            {
                return submitRpc(client);
            }
            //else
            {
                return submitRest(client);
            }
        }

        /// <summary> Collects all of the queued requests and encodes them into a single JSON
        /// string before creating a new HTTP request, attaching this string to the
        /// request body, signing it, and sending it to the container.
        /// </summary>
        /// <param name="client">OpenSocialClient object with RPC_ENDPOINT property set
        /// </param>
        /// <returns> Object encapsulating the data requested from the container
        /// </returns>
        /// <throws>  OpenSocialRequestException </throws>
        /// <throws>  JSONException </throws>
        /// <throws>  OAuthException </throws>
        /// <throws>  IOException </throws>
        /// <throws>  URISyntaxException </throws>
        private OpenSocialResponse submitRpc(OpenSocialClient client)
        {
            string rpcEndpoint = client.getProperty(OpenSocialClient.Properties.RPC_ENDPOINT);

            var requestArray = new Jayrock.Json.JsonArray();
            foreach (IOpenSocialRequest r in requests)
            {
                requestArray.Add(Jayrock.Json.Conversion.JsonConvert.Import(r.ToJson()));
            }

            var requestUrl = new OpenSocialUrl(rpcEndpoint);

            var request = new OpenSocialHttpRequest("POST", requestUrl)
                              {
                                  ContentType = client.ContentType,
                                  Body = requestArray.ToString()
                              };

            if (!string.IsNullOrEmpty(client.ConsumerSecret)) // Else, useless to sign
                OpenSocialOAuthClient.signRequest(request, client);

            if (System.Diagnostics.Debugger.IsAttached)
            {
                System.Diagnostics.Debug.WriteLine("Request URL:\n" + request.getUrl());
                System.Diagnostics.Debug.WriteLine("Request body:\n" + request.Body);
            }

            string responseString = request.execute();

            if (System.Diagnostics.Debugger.IsAttached)
                System.Diagnostics.Debug.WriteLine("Response:\n" + responseString + "\n");

            return OpenSocialJsonParser.getResponse(responseString);
        }

        /// <summary> Retrieves the first request in the queue and builds the full REST URI
        /// given the properties of this request before creating a new HTTP request,
        /// signing it, and sending it to the container.
        /// </summary>
        /// <param name="client">OpenSocialClient object with REST_BASE_URI property set
        /// </param>
        /// <returns> Object encapsulating the data requested from the container
        /// </returns>
        /// <throws>  OpenSocialRequestException </throws>
        /// <throws>  JSONException </throws>
        /// <throws>  OAuthException </throws>
        /// <throws>  IOException </throws>
        /// <throws>  URISyntaxException </throws>
        private OpenSocialResponse submitRest(OpenSocialClient client)
        {
            if (requests.Count != 1)
                throw new OpenSocialRequestException("Rest doesn't support batch"); // TODO: Simulate batch

            string restBaseUri = client.getProperty(OpenSocialClient.Properties.REST_BASE_URI);

            IOpenSocialRequest r = requests[0];

            var requestUrl = new OpenSocialUrl(restBaseUri);

            requestUrl.addPathComponent(r.RestPathComponent); // TODO: Error-prone since new params must be manually added
            if (r.hasParameter("userId"))
                requestUrl.addPathComponent(r.popParameter("userId"));
            if (r.hasParameter("groupId"))
                requestUrl.addPathComponent(r.popParameter("groupId"));
            if (r.hasParameter("appId"))
                requestUrl.addPathComponent(r.popParameter("appId"));
            if (r.hasParameter("albumId"))
                requestUrl.addPathComponent(r.popParameter("albumId"));
            if (r.hasParameter("mediaItemId"))
                requestUrl.addPathComponent(r.popParameter("mediaItemId"));

            if (r.hasParameter("contentType"))
            {
                client.ContentType = r.popParameter("contentType");
            }

            string method = r.RestMethod;
            var request = new OpenSocialHttpRequest(method, requestUrl)
                              {
                                  ContentType = client.ContentType
                              };

            if (method.Equals("PUT") || method.Equals("POST"))
            {
                if (r.hasParameter("data"))
                {
                    request.Body = r.popParameter("data");
                }
            }

            foreach (var entry in r.Parameters)
                requestUrl.addQueryStringParameter(entry.Key, entry.Value.getValuesString());

            if (!string.IsNullOrEmpty(client.ConsumerSecret))
            { // Else, useless to sign

                // do not sign the image bytes...
                if (r.RpcMethod.CompareTo("mediaitems.upload") == 0 && r.RestMethod.CompareTo("POST") == 0)
                {
                    string saveBody = request.Body;
                    request.Body = null;
                    OpenSocialOAuthClient.signRequest(request, client);
                    request.Body = saveBody;
                }
                else
                {
                    OpenSocialOAuthClient.signRequest(request, client);
                }
            }

            if (System.Diagnostics.Debugger.IsAttached)
            {
                System.Diagnostics.Debug.WriteLine("Request URL:\n" + request.getUrl());
                System.Diagnostics.Debug.WriteLine("Request body:\n" + request.Body);
            }

            string responseString = request.execute(r.RpcMethod);

            if (System.Diagnostics.Debugger.IsAttached)
                System.Diagnostics.Debug.WriteLine("Response:\n" + responseString + "\n");

            return OpenSocialJsonParser.getResponse(responseString, r.Id);
        }
    }
}